/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/module/ModuleManagement.h"
#include "simodo/interpret/Interpret.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/format/fmt.h"

#include <cstdio>
#include <cassert>

constexpr int BUFFER_SIZE = 1024;

namespace simodo::module
{
    bool ModuleManagement::debug(const inout::uri_set_t & files)
    {
        bool     done    = true;

        std::unique_lock<std::mutex> locking(_debug_condition_mutex);
        if (_timeout > 0)
            /// \todo PVS Studio: warn V1089 Waiting on condition variable without predicate.
            /// A thread can wait indefinitely or experience a spurious wakeup. 
            /// Consider passing a predicate as the third argument.
            done = _debug_condition.wait_for(locking, std::chrono::seconds(_timeout)) == std::cv_status::no_timeout;
        else
            /// \todo PVS Studio: warn V1089 Waiting on condition variable without predicate.
            /// A thread can wait indefinitely or experience a spurious wakeup. 
            /// Consider passing a predicate as the third argument.
            _debug_condition.wait(locking);

        const loom::Fiber_interface *     causer = _loom.causer();
        std::vector<loom::FiberStructure> fibers = _loom.fibers();

        if (done && !causer) {
            _loom.finish();
            return true;
        }

        if (causer)
            _m.reportInformation(inout::fmt("Остановка программы произошла из-за нити %1")
                                 .arg(getFiberName(findFiberStructure(fibers,causer))));
        else {
            _loom.pause();
            assert(_loom.paused());
            
            _m.reportInformation(inout::fmt("Программа работала дольше %1 сек и была остановлена")
                                .arg(_timeout));
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        _m.reportInformation(inout::fmt("Индексы исходных файлов:"));
        for(size_t i=0; i < files.size(); ++i)
            _m.reportInformation(std::to_string(i) + ". " + files[i]);

        _m.reportInformation(inout::fmt("Структура нитей (name/no, status, (file,line,host,op), need_delete, waiting_for_no, error? causer?):"));

        fibers = _loom.fibers();

        bool errors = false;
        for(const loom::FiberStructure & fs : fibers) {
            if (fs.p_parent == nullptr)
                printFiberBranch(fibers, fs, causer);

            if (fs.status == loom::FiberStatus::Paused) {
                interpret::Interpret_interface * p_interpret = dynamic_cast<interpret::Interpret_interface *>(fs.p_fiber);
                if (p_interpret && p_interpret->errors()) {
                    errors = true;
                    break;
                }
            }
        }

        if (_resume && !errors) {
            _loom.resume();
            _loom.finish();
            return true;
        }
        
        _loom.stop();
        return false;
    }

    ///////////////////////////////////////////////////////////////////////////////
    // Private

    void ModuleManagement::printFiberBranch(
                     const std::vector<loom::FiberStructure> & fibers, 
                     const loom::FiberStructure & fs, 
                     const loom::Fiber_interface * causer,
                     int depth) const
    {
        std::string info, err;
        interpret::Interpret_interface * p_interpret = nullptr;

        if (fs.status == loom::FiberStatus::Flow)
            info = "(*)";
        else {
            p_interpret = dynamic_cast<interpret::Interpret_interface *>(fs.p_fiber);

            if (p_interpret) {
                info = getInfo(p_interpret->lookupOperationNode());
                err = p_interpret->errors() ? ", error" : "";
            }
            else
                info = "?";
        }

        if (fs.p_fiber == causer)
            err += " causer";

        char buffer[BUFFER_SIZE];

        snprintf(buffer, BUFFER_SIZE, 
                "%s> %s, %s, %s, %s, %s%s", 
                std::string(depth,'-').c_str(), 
                getFiberName(fs).c_str(),
                loom::getFiberStatusName(fs.status),
                info.c_str(),
                (fs.need_delete ? "yes" : "no"),
                fs.p_waiting_for ? getFiberName(findFiberStructure(fibers,fs.p_waiting_for)).c_str() : "-",
                err.c_str());

        _m.reportInformation(buffer);

        if (p_interpret)
            if (fs.status == loom::FiberStatus::Paused || fs.status == loom::FiberStatus::Delayed)
                if (_need_full_debug_info || fs.p_fiber == causer)
                    printCallStack(*p_interpret);

        for(const loom::FiberStructure & child : fibers)
            if (child.p_parent == fs.p_fiber)
                printFiberBranch(fibers, child, causer, depth+1);
    }

    void ModuleManagement::printCallStack(const interpret::Interpret_interface & interp) const
    {
        std::vector<interpret::boundary_index_t> frames = interp.stack().getFunctionBounds();

        for(size_t i=0; i < frames.size(); ++i) {
            interpret::boundary_index_t fi                = frames[i];
            auto                        [begin, end]      = interp.stack().boundaryLowAndTop(fi);
            interpret::name_index_t     function_index    = begin - 1;
            const variable::Variable    function_variable = function_index >= interp.stack().index_over_top() 
                                                          ? variable::error_variable()
                                                          : interp.stack().variable(function_index);
            std::string text;

            if (i > 0) {
                    const ast::Node * p_node = interp.lookupOperationNode(0,frames[i]);

                    if (p_node)
                        text = inout::fmt("\tcalled from %1:").arg(getInfo(p_node));
                    else if (function_variable.type() == variable::ValueType::Ref) {
                        text = inout::fmt("\tcall point is out, formal call location: (f:%1, l:%2):")
                                .arg(function_variable.location().uri_index())
                                .arg(function_variable.location().range().start().line()+1);
                    }
                    else
                        text = inout::fmt("\tcall point is out");

                _m.reportInformation(text);
                text = {};
            }

            if (function_variable.origin().value().isFunction()) {
                variable::FunctionWrapper    function(function_variable);
                const variable::Variable &   return_type    = function.getReturnDeclarationVariable();
                variable::VariableSetWrapper declarations   = function.getArgumentDeclarationVariables();

                text += "\tfn " + inout::toU8(function_variable.origin().name())
                      + "(";
                for(size_t i=0; i < declarations.size(); ++i) {
                    if (i > 0)
                        text += ", ";

                    text += variable::getValueTypeName(declarations[i].type());

                    if (!declarations[i].name().empty())
                        text += " " + inout::toU8(declarations[i].name());
                }
                text += ")";
                if (return_type.type() != variable::ValueType::Null)
                    text += " -> " + variable::getValueTypeName(return_type.type());
            }
            else
                text += "\t<root module>";

            _m.reportInformation(text);

            printLocals(interp, fi);
        }
    }

    void ModuleManagement::printLocals(const interpret::Interpret_interface & causer, 
                    interpret::boundary_index_t frame_index) const
    {
        std::vector<interpret::boundary_index_t> frames = causer.stack().getFunctionBounds();

        assert(!frames.empty());

        if (frame_index > frames.front())
            frame_index = frames.front();

        std::vector<interpret::name_index_t> locals = causer.stack().getFrameLocals(frame_index);

        for(interpret::name_index_t i : locals) {
            const variable::Variable &   v = causer.stack().variable(i);

            _m.reportInformation("\t\t" + inout::toU8(v.origin().name()) + " = " + inout::toU8(variable::toString(v.origin().value())));
        }
    }

    std::string ModuleManagement::getInfo(const ast::Node * p) const
    {
        std::string info;

        if (p)
            info = "f:" + std::to_string(int(p->token().location().uri_index())) + ", "
                 + "l:" + std::to_string(p->token().location().range().start().line()+1) + ", "
                 + "h:" + inout::toU8(p->host()) + ", "
                 + "o:" + std::to_string(int(p->operation()));
        else
            info = "-";

        return "(" + info + ")";
    }

    std::string ModuleManagement::getFiberName(const loom::FiberStructure & fs) const
    {
        if (fs.name.empty())
            return std::to_string(int(fs.no));

        return fs.name + "/" + std::to_string(int(fs.no));
    }

    const loom::FiberStructure &ModuleManagement::findFiberStructure(const std::vector<loom::FiberStructure> & fibers, const loom::Fiber_interface * p_fiber) const
    {
        static const loom::FiberStructure none {nullptr,nullptr,false,loom::FiberStatus::Complete,0,{}};

        auto const it = std::find_if(fibers.begin(), fibers.end(), [p_fiber](const loom::FiberStructure & fs){
                        return fs.p_fiber == p_fiber;                        
                    });

        if (it == fibers.end())
            return none;

        return *it;
    }

}
